#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>


static struct cdev remap_demo_cdev;
static struct class* remap_demo_cls;
static struct device* remap_demo_device;
static dev_t remap_demo_devno;

static const char* class_name = "remap_demo";
static const char* module_name = "remap_demo";
static const char* device_name = "remap_demo";

static void* shm = NULL;
static int32_t shm_size = 0;

static int remap_demo_open(struct inode* inode, struct file* filp)
{
    return 0;
}

static int remap_demo_release(struct inode* inode, struct file* filp)
{
    return 0;
}

static int device_mmap(struct file *filp, struct vm_area_struct *vma) {
    int ret = 0;

    if (shm == NULL)
    {
        shm_size = (int32_t)(vma->vm_end - vma->vm_start);
        printk(KERN_INFO "shm_size = %d\n", shm_size);
        shm = vmalloc_user(shm_size);
        printk("shm = %p\n", shm);
    }

    ret = remap_vmalloc_range(vma, shm, 0);
    if (ret != 0) {
        printk(KERN_INFO "remap_vmalloc_range failed with %d\n", ret);
        return -EAGAIN;
    }
    return 0;
}


static const struct file_operations remap_demo_fops =
{
    .owner = THIS_MODULE,
    .open = remap_demo_open,
    .release = remap_demo_release,
    .mmap = device_mmap,
};

static int __init remap_demo_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&remap_demo_devno, 0, 1, module_name);
    if (0 != ret)
    {
        return ret;
    }

    cdev_init(&remap_demo_cdev, &remap_demo_fops);
    remap_demo_cdev.owner = THIS_MODULE;
    ret = cdev_add(&remap_demo_cdev, remap_demo_devno, 1);
    if (ret != 0)
    {
        goto fail1;
    }

    remap_demo_cls = class_create(THIS_MODULE, class_name);
    if (IS_ERR(remap_demo_cls))
    {
        ret = PTR_ERR(remap_demo_cls);
        goto fail2;
    }

    remap_demo_device = device_create(remap_demo_cls, NULL, remap_demo_devno, NULL, device_name);
    if (IS_ERR(remap_demo_device))
    {
        ret = PTR_ERR(remap_demo_device);
        goto fail3;
    }
    return 0;

fail3:
    class_destroy(remap_demo_cls);
fail2:
    cdev_del(&remap_demo_cdev);
fail1:
    unregister_chrdev_region(remap_demo_devno, 1);
    return ret;
}
module_init(remap_demo_init);

static void __exit remap_demo_exit(void)
{
    if (NULL != shm)
    {
        int* p = (int*)shm;
        int isize = shm_size >> 2;
        printk(KERN_INFO "shm_size = %d. test value: %d %d %d\n", shm_size, p[0], p[isize/2], p[isize-1]);  
        vfree(shm);
        shm = NULL;
        shm_size = 0;
    }

    device_destroy(remap_demo_cls, remap_demo_devno);
    class_destroy(remap_demo_cls);
    cdev_del(&remap_demo_cdev);
    unregister_chrdev_region(remap_demo_devno, 1);
}
module_exit(remap_demo_exit);

MODULE_LICENSE("GPL");
